<

メモリビューの使用

メモリビューは詳細な洞察を提供します アプリケーションのメモリ割り当てと 特定の問題を検出してデバッグするためのツール。

さまざまな IDE で DevTools 画面を見つける方法については、 をチェックしてください開発ツールの概要

このページにある洞察をより深く理解するには、 最初のセクションでは、Dart がメモリをどのように管理するかを説明します。 Dart のメモリ管理をすでに理解している場合は、 までスキップできますメモリビューガイド

メモリビューを使用する理由

メモリ ビューは、プリエンプティブなメモリ最適化のため、または次の場合に使用します。 アプリケーションで次のいずれかの状況が発生しています。

  • メモリ不足になるとクラッシュする
  • 速度が遅くなる
  • デバイスの速度が低下したり、応答しなくなったりする
  • メモリ制限を超えたため、オペレーティング システムによって強制的にシャットダウンされます
  • メモリ使用量の制限を超えています
    • この制限は、アプリが対象とするデバイスの種類によって異なる場合があります。
  • メモリリークの疑い

メモリの基本概念

クラスコンストラクターを使用して作成された Dart オブジェクト (たとえば、MyClass()) に住んでいます と呼ばれるメモリの部分ヒープ。想い出 ヒープ内のデータは Dart VM (仮想マシン) によって管理されます。 Dart VM はオブジェクトの作成時にオブジェクトにメモリを割り当てます。 オブジェクトが削除されたときにメモリを解放 (または割り当て解除) します。 はもう使用されていません(を参照)ダーツのゴミ収集)。

ルートオブジェクト、保持パス、および到達可能性

ルートオブジェクト

すべての Dart アプリケーションはルートオブジェクトそれを参照すると、 直接的または間接的に、アプリケーションが割り当てる他のすべてのオブジェクト。

到達可能性

アプリケーションの実行中のある時点で、 ルート オブジェクトは割り当てられたオブジェクトの参照を停止します。 オブジェクトは次のようになります到達不可能な、 これはガベージ コレクター (GC) への信号です。 オブジェクトのメモリの割り当てを解除します。

擁壁

ルートからオブジェクトへの参照のシーケンス オブジェクトと呼ばれます保持する道、 ガベージ コレクションからのオブジェクトのメモリが保持されるためです。 1 つのオブジェクトに多数の保持パスを含めることができます。 少なくとも 1 つの保持パスを持つオブジェクトは、 呼ばれた到達可能なオブジェクト。

次の例は概念を示しています。

class Child{}

class Parent {
  Child? child;
}

Parent parent1 = Parent();

void myFunction() {

  Child? child = Child();

  // The `child` object was allocated in memory.
  // It's now retained from garbage collection
  // by one retaining path (root …-> myFunction -> child).

  Parent? parent2 = Parent()..child = child;
  parent1.child = child;

  // At this point the `child` object has three retaining paths:
  // root …-> myFunction -> child
  // root …-> myFunction -> parent2 -> child
  // root -> parent1 -> child

  child = null;
  parent1.child = null;
  parent2 = null;

  // At this point, the `child` instance is unreachable
  // and will eventually be garbage collected.

  
}

浅いサイズと保持されたサイズ

浅いサイズオブジェクトのサイズのみが含まれます とその参考文献、保持サイズも含まれます 保持されるオブジェクトのサイズ。

保持サイズルートオブジェクトには以下が含まれます 到達可能なすべての Dart オブジェクト。

次の例では、サイズはmyHugeInstance親または子のシャロー サイズの一部ではありません。 ただし、これは保持されるサイズの一部です。

class Child{
  /// The instance is part of both [parent] and [parent.child]
  /// retained sizes.
  final myHugeInstance = MyHugeInstance();
}

class Parent {
  Child? child;
}

Parent parent = Parent()..child = Child();

DevTools の計算では、オブジェクトにさらに多くの値がある場合、 1 つの保持パスよりも大きい場合、そのサイズは次のように割り当てられます。 最短の保持パスのメンバーのみに保持されます。

この例では、オブジェクトxには 2 つの保持パスがあります。

root -> a -> b -> c -> x
root -> d -> e -> x (shortest retaining path to `x`)

最短パスのメンバーのみ (de) が含まれますx保持サイズに合わせます。

Dart でメモリ リークが発生する?

ガベージ コレクターはすべての種類のメモリ リークを防ぐことはできません。 リークのないライフサイクルを保つためにオブジェクトを監視する必要があります。

なぜガベージコレクターはすべての漏れを防ぐことができないのでしょうか?

ガベージコレクターがすべてを処理しますが、 到達不可能なオブジェクト、それは責任です アプリケーションの不要なオブジェクトを確実に削除する は到達できなくなりました (ルートから参照されます)。

したがって、必要のないオブジェクトが参照されたままになっている場合、 (グローバル変数または静的変数では、 または長命オブジェクトのフィールドとして)、 ガベージコレクターはそれらを認識できません。 メモリ割り当ては徐々に増加します。 そしてアプリは最終的にクラッシュしてしまいますout-of-memoryエラー。

クロージャに特別な注意が必要な理由

捕捉するのが難しいリーク パターンの 1 つは、クロージャの使用に関連しています。 次のコードでは、 短命になるように設計されているmyHugeObject暗黙的に クロージャコンテキストに保存され、に渡されますsetHandler。 結果として、myHugeObjectガベージコレクションされません に限ってhandler到達可能です。

  final handler = () => print(myHugeObject.name);
  setHandler(handler);

なぜBuildContext特別な注意が必要です

大きくて寿命が短いオブジェクトの例 長期間居住するエリアに押し込まれ、漏れが発生する可能性があります。 それはcontextFlutterに渡されるパラメータbuild方法。

次のコードはリークが発生しやすいため、 としてuseHandlerハンドラーを保存する可能性があります 長住地域では:

// BAD: DO NOT DO THIS
// This code is leak prone:
@override
Widget build(BuildContext context) {
  final handler = () => apply(Theme.of(context));
  useHandler(handler);

リークしやすいコードを修正するにはどうすればよいですか?

次のコードはリークしにくいですが、 なぜなら:

  1. クロージャーは、大きくて寿命が短いものを使用しません。context物体。
  2. theme(代わりに使用される) オブジェクトは長寿命です。それは一度作成され、 間で共有されるBuildContextインスタンス。
// GOOD
@override
Widget build(BuildContext context) {
  final theme = Theme.of(context);
  final handler = () => apply(theme);
  useHandler(handler);

一般的なルールBuildContext

一般に、次のルールを使用します。BuildContext: クロージャが存続しない場合 ウィジェットの場合は、コンテキストをクロージャに渡しても問題ありません。

ステートフル ウィジェットには特別な注意が必要です。 これらは 2 つのクラスで構成されます。ウィジェットと ウィジェットの状態、 ウィジェットの寿命が短い場合、 そして国家は長生きする。ビルドコンテキスト、 ウィジェットによって所有されているため、決して参照しないでください 州はゴミにはならないので、州の畑から ウィジェットと一緒に収集されるため、ウィジェットよりも大幅に長く存続する可能性があります。

メモリリークとメモリ肥大化

メモリ リークが発生すると、アプリケーションはメモリを徐々に使用し、 たとえば、リスナーを繰り返し作成することで、 でも処分はしない。

メモリの肥大化により、必要以上のメモリが使用されます。 最適なパフォーマンスを実現するには、たとえば、大きすぎるものを使用します。 画像を保存したり、ストリームを存続期間中開いたままにしたりできます。

漏れも膨満も大きくなると、 アプリケーションがクラッシュする原因となるout-of-memoryエラー。 ただし、リークはメモリの問題を引き起こす可能性が高く、 たとえ小さな漏れでも、 何度も繰り返すとクラッシュにつながります。

メモリビューガイド

DevTools のメモリ ビューは調査に役立ちます メモリ割り当て (ヒープ内と外部の両方)、 メモリリーク、メモリ肥大化など。景色 には次のような特徴があります。

展開可能なチャート
メモリ割り当ての高レベルのトレースを取得します。 両方の標準イベント (ガベージ コレクションなど) を表示します カスタム イベント (画像の割り当てなど)。
プロファイルメモリタブ
現在のメモリ割り当てをクラスごとにリストして確認し、 メモリータイプ。
差分スナップショットタブ
機能のメモリ管理の問題を検出して調査します。
トレースインスタンスタブ
機能のメモリ管理を調査する 指定されたクラスのセット。

展開可能なチャート

展開可能なグラフには次の機能があります。

記憶の解剖学

タイムリーグラフはの状態を視覚化します 連続した時間間隔での flutterメモリ。 チャート上の各データポイントは、に対応します 測定量のタイムスタンプ(x軸)(y軸) ヒープの。たとえば、使用法、容量、外部、 ごみ収集、および居住者のセットサイズがキャプチャされます。

Screenshot of a memory anatomy page

メモリ概要図

メモリ概要グラフは時系列グラフです 収集されたメモリ統計の。視覚的に提示してくれる Dart または Flutter ヒープと Dart の状態 または、時間の経過に伴う Flutter のネイティブ メモリ。

グラフの X 軸はイベントのタイムライン (時系列) です。 Y 軸にプロットされたデータにはすべて、次のタイムスタンプが付いています。 データが収集されたとき。言い換えると、 ポーリングされた状態 (容量、使用済み、外部、 RSS (常駐セット サイズ)、および GC (ガベージ コレクション)) 500 ミリ秒ごとにメモリが削除されます。これはライブを提供するのに役立ちます アプリケーションの実行中にメモリの状態が表示されます。

をクリックすると、伝説ボタンに表示されるのは、 収集された測定値、記号、色 データを表示するために使用されます。

Screenshot of a memory anatomy page

メモリサイズスケールy 軸を自動的に で収集されたデータの範囲に合わせて調整します。 現在表示されているチャートの範囲。

Y 軸にプロットされた量は次のとおりです。

ダーツ/ flutterヒープ
ヒープ内のオブジェクト (Dart オブジェクトと Flutter オブジェクト)。
ダーツ/ flutterネイティブ
Dart/Flutter ヒープにないメモリ ただし、それでも合計メモリ フットプリントの一部です。 このメモリ内のオブジェクトはネイティブ オブジェクトになります (たとえば、ファイルをメモリに読み込むとき、 またはデコードされた画像)。ネイティブオブジェクトが公開される ネイティブ OS (Android、 Linux、Windows、iOS) Dart エンベッダーを使用します。 エンベッダーはファイナライザーを備えた Dart ラッパーを作成します。 Dart コードがこれらのネイティブ リソースと通信できるようにします。 Flutter には Android と iOS 用のエンベッダーがあります。 ために詳細については、を参照してくださいコマンドラインおよびサーバーアプリ、ダーツフロッグを使ってサーバー上でダーツをする、カスタム flutterエンジンエンベッダー、Heroku を使用した Dart Web サーバーのデプロイメント。
タイムライン
収集されたすべてのメモリ統計のタイムスタンプ 特定の時点のイベント (タイムスタンプ)。
ラスターキャッシュ
Flutter エンジンのラスター キャッシュのサイズ レイヤーまたは画像を実行しながら、 合成後の最終レンダリング。 詳細については、「Flutter アーキテクチャの概要DevTools のパフォーマンス ビュー
割り当て済み
現在のヒープ容量は通常、 すべてのヒープ オブジェクトの合計サイズよりわずかに大きくなります。
RSS - 常駐セットのサイズ
常駐セットのサイズはメモリの量を表示します プロセスのために。 スワップアウトされたメモリは含まれません。 これには、次のような共有ライブラリからのメモリが含まれます。 ロードされているだけでなく、すべてのスタックおよびヒープ メモリもロードされます。 詳細については、を参照してください。Dart VM の内部構造。

「プロファイルメモリ」タブ

使用プロファイルメモリタブで現在のメモリを確認します クラスとメモリタイプによる割り当て。のために Google スプレッドシートやその他のツールでのより詳細な分析、 データをCSV形式でダウンロードします。 トグルGC で更新、リアルタイムで割り当てを確認します。

Screenshot of the profile tab page

「差分スナップショット」タブ

使用差分スナップショット機能を調査するためのタブ メモリ管理。タブのガイダンスに従ってください インタラクションの前後でスナップショットを取得する アプリケーションでスナップショットの差分を確認します。

Screenshot of the diff tab page

をタップします。クラスとパッケージをフィルタリングするボタン、 データを絞り込むには:

Screenshot of the filter options ui

Google スプレッドシートでより詳細な分析を行うには または他のツールを使用して、データを CSV 形式でダウンロードします。

「トレースインスタンス」タブ

使用トレースインスタンスどの方法を調査するためのタブ 機能の実行中に一連のクラスにメモリを割り当てます。

  1. トレースするクラスの選択
  2. アプリと対話してコードをトリガーします に興味があります
  3. タップリフレッシュ
  4. トレースされたクラスを選択します
  5. 収集したデータを確認する

Screenshot of a trace tab

ボトムアップビューとコールツリービュー

ボトムアップ ビューとコール ツリー ビューを切り替える タスクの詳細に応じて。

Screenshot of a trace allocations

コール ツリー ビューにはメソッドの割り当てが表示されます インスタンスごとに。ビューはトップダウン表現です コールスタックの、メソッドを展開できることを意味します 呼び出し先を表示します。

ボトムアップ ビューには、さまざまなリストが表示されます。 インスタンスを割り当てた呼び出しスタック。

その他のリソース

詳細については、次のリソースを確認してください。

  • アプリのメモリ使用量を監視する方法を学ぶには DevTools を使用してメモリ リークを検出します。 ガイド付きをチェックしてくださいメモリビューのチュートリアル。
  • Android のメモリ構造を理解するには、 チェックアウトAndroid: プロセス間のメモリ割り当て。
ce33​​84d1-0b81-4851-9191-443bdb855c55